/*
 * Copyright (c) 2004, PostgreSQL Global Development Group
 * See the LICENSE file in the project root for more information.
 *
 * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd
 */

package org.postgresql.jdbc;

import org.postgresql.Driver;
import org.postgresql.core.ORCachedQuery;
import org.postgresql.core.ORParameterList;
import org.postgresql.core.ORDataType;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

import java.io.Reader;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.net.URL;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Types;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Array;
import java.sql.Ref;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.ParameterMetaData;
import java.sql.NClob;
import java.sql.SQLXML;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Calendar;

/**
 * the prepare statement.
 *
 * @author zhangting
 * @since  2025-06-29
 */
public class ORPreparedStatement extends ORStatement implements PreparedStatement {
    private static Log LOGGER = Logger.getLogger(ORPreparedStatement.class.getName());
    private static final int TC_SQL = 4;

    /**
     * prepared parameters
     */
    protected ORParameterList preparedParameters;

    /**
     * has param
     */
    protected boolean hasParam;
    private final ORCachedQuery preparedQuery;

    /**
     * preparedStatement constructor
     *
     * @param conn connection
     * @param sql sql
     * @param resultSetType resultSetType
     * @param resultSetConcurrency resultSetConcurrency
     * @param resultSetHoldability resultSetHoldability
     */
    public ORPreparedStatement(ORConnection conn, String sql, int resultSetType, int resultSetConcurrency,
                               int resultSetHoldability) {
        super(conn, resultSetType, resultSetConcurrency, resultSetHoldability);
        this.preparedQuery = new ORCachedQuery(connection, this, sql, true);
        this.parametersList = new ArrayList<>();
        this.preparedParameters = new ORParameterList(this.preparedQuery.getParamCount());
    }

    @Override
    public boolean execute() throws SQLException {
        return executeWithFlags();
    }

    private boolean executeWithFlags() throws SQLException {
        verifyClosed();
        if (!hasParam && preparedParameters.getParamCount() > 0) {
            addParameters();
        }
        preparedQuery.setSql(preparedQuery.getParsedSql());
        try {
            execute(preparedQuery, parametersList);
            if (queryMode == TC_SQL) {
                getCursorResultSet();
            }
            if (preparedQuery.isAutoGeneratedKeys()) {
                this.generatedKeys = preparedQuery.getGeneratedKeys();
            }
            synchronized (this) {
                verifyClosed();
                return rs != null;
            }
        } finally {
            parametersList.clear();
        }
    }

    private void getCursorResultSet() throws SQLException {
        for (long cursorId : cursorSets) {
            ResultSet cursorRs = getRefCursor(cursorId);
            resultSets.add(cursorRs);
        }
        if (!resultSets.isEmpty()) {
            this.rs = resultSets.get(0);
        }
    }

    /**
     * get cursor ResultSet
     *
     * @param cursorId cursorId
     * @return ResultSet
     * @throws SQLException if a database access error occurs
     */
    protected ResultSet getRefCursor(long cursorId) throws SQLException {
        ORCursorResultSet cursorRs = null;
        Statement stmt = connection.createStatement();
        if (stmt instanceof ORStatement) {
            long statId = cursorId >> 32;
            ORStatement orStmt = (ORStatement) stmt;
            orStmt.setMark((int) statId);
            long cursorMode = cursorId & -1L;
            cursorRs = new ORCursorResultSet(orStmt, (int) cursorMode);
            connection.getQueryExecutor().fetchCursor(cursorRs);
            cursorRs.setFetched(true);
        }
        return cursorRs;
    }

    @Override
    public int[] executeBatch() throws SQLException {
        if (!hasParam) {
            return new int[0];
        }
        verifyClosed();
        try {
            boolean isAutoCommit = connection.getAutoCommit();
            connection.setAutoCommit(false);
            int[] updateCounts;
            if (!parametersList.isEmpty()) {
                updateCounts = new int[parametersList.size()];
            } else {
                updateCounts = new int[1];
            }
            try {
                preparedQuery.setSql(preparedQuery.getParsedSql());
                execute(preparedQuery, parametersList);
                if (preparedQuery.isAutoGeneratedKeys()) {
                    this.generatedKeys = preparedQuery.getGeneratedKeys();
                }
                updateCounts[0] = getUpdateCount();
            } catch (SQLException e) {
                if (isAutoCommit) {
                    rollback(connection);
                }
                throw new SQLException(e.getMessage(), e);
            } finally {
                connection.setAutoCommit(isAutoCommit);
            }

            if (isAutoCommit) {
                commit(connection);
            }
            return updateCounts;
        } finally {
            this.parametersList.clear();
            this.hasParam = false;
        }
    }

    /**
     * get preparedQuery
     *
     * @return preparedQuery
     */
    public ORCachedQuery getPreparedQuery() {
        return preparedQuery;
    }

    @Override
    public ResultSet executeQuery() throws SQLException {
        if (!executeWithFlags()) {
            throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA);
        }
        return rs;
    }

    private void commit(Connection con) throws SQLException {
        try {
            con.commit();
        } catch (SQLException e) {
            throw new SQLException("commit failed.", e);
        }
    }

    private void rollback(Connection con) throws SQLException {
        try {
            con.rollback();
        } catch (SQLException e) {
            throw new SQLException("rollback failed.", e);
        }
    }

    @Override
    public void addBatch() throws SQLException {
        verifyClosed();
        addParameters();
        hasParam = true;
    }

    @Override
    public int executeUpdate() throws SQLException {
        verifyClosed();
        if (!hasParam) {
            addParameters();
        }
        preparedQuery.setSql(preparedQuery.getParsedSql());
        try {
            execute(preparedQuery, parametersList);
            if (preparedQuery.isAutoGeneratedKeys()) {
                this.generatedKeys = preparedQuery.getGeneratedKeys();
            }
            return getUpdateCount();
        } finally {
            this.parametersList.clear();
        }
    }

    @Override
    public void setBoolean(int index, boolean x) throws SQLException {
        verifyClosed();
        int value = x ? 1 : 0;
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.INT, value);
    }

    private void setBinary(int index, Object x) throws SQLException {
        byte[] value;
        if (x instanceof byte[]) {
            value = (byte[]) x;
        } else {
            value = String.valueOf(x).getBytes(connection.getORStream().getCharset());
        }
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.BINARY, value);
    }

    @Override
    public void setByte(int index, byte x) throws SQLException {
        verifyClosed();
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.INT, Integer.valueOf(x));
    }

    @Override
    public void setShort(int index, short x) throws SQLException {
        verifyClosed();
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.INT, Integer.valueOf(x));
    }

    @Override
    public void setInt(int index, int x) throws SQLException {
        verifyClosed();
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.INT, x);
    }

    @Override
    public void setLong(int index, long x) throws SQLException {
        verifyClosed();
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.BIGINT, x);
    }

    @Override
    public void setFloat(int index, float x) throws SQLException {
        verifyClosed();
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.REAL, x);
    }

    @Override
    public void setDouble(int index, double x) throws SQLException {
        verifyClosed();
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.REAL, x);
    }

    @Override
    public void setBigDecimal(int index, BigDecimal x) throws SQLException {
        verifyClosed();
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.NUMERIC, x);
    }

    /**
     * add parameters
     *
     * @throws SQLException if a database access error occurs
     */
    protected void addParameters() throws SQLException {
        preparedParameters.checkAllParametersSet();
        if (!parametersList.isEmpty()) {
            int paramCount = preparedParameters.getParamCount();
            ORParameterList params = parametersList.get(0);
            for (int i = 0; i < paramCount; i++) {
                if (preparedParameters.getDbTypes()[i] != params.getDbTypes()[i]) {
                    preparedParameters.transferType(connection.getORStream(), i, params.getDbTypes()[i]);
                }
            }
        }
        parametersList.add(preparedParameters);
        preparedParameters = new ORParameterList(preparedQuery.getParamCount());
    }

    @Override
    public void setString(int index, String x) throws SQLException {
        verifyClosed();
        if (x == null) {
            setNull(index, Types.VARCHAR);
        } else {
            preparedParameters.bindParam(connection.getORStream(), index, ORDataType.TEXT, x);
        }
    }

    @Override
    public void setNString(int index, String value) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setNString(int,String)");
    }

    @Override
    public void setBytes(int index, byte[] x) throws SQLException {
        verifyClosed();
        if (x == null) {
            setNull(index, Types.VARBINARY);
        } else {
            preparedParameters.bindParam(connection.getORStream(), index, ORDataType.BINARY, x);
        }
    }

    @Override
    public void setTime(int index, Time x) throws SQLException {
        verifyClosed();
        setTime(index, x, null);
    }

    @Override
    public void setAsciiStream(int index, InputStream x, int length) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setAsciiStream(int, InputStream, long)");
    }

    @Override
    public void setUnicodeStream(int index, InputStream x, int length) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setUnicodeStream(int, InputStream, long)");
    }

    @Override
    public void setNull(int index, int sqlType) throws SQLException {
        verifyClosed();
        int dataType;
        switch (sqlType) {
            case Types.BOOLEAN:
            case Types.BIT:
            case Types.INTEGER:
            case Types.TINYINT:
            case Types.SMALLINT:
                dataType = ORDataType.INT;
                break;
            case Types.DOUBLE:
            case Types.FLOAT:
            case Types.REAL:
                dataType = ORDataType.REAL;
                break;
            case Types.BIGINT:
                dataType = ORDataType.BIGINT;
                break;
            case Types.DATE:
                dataType = ORDataType.DATE;
                break;
            case Types.TIME:
                dataType = ORDataType.TIME;
                break;
            case Types.TIMESTAMP:
                dataType = ORDataType.TIMESTAMP;
                break;
            case Types.BINARY:
            case Types.VARBINARY:
            case Types.LONGVARBINARY:
                dataType = ORDataType.BINARY;
                break;
            case Types.DECIMAL:
            case Types.NUMERIC:
                dataType = ORDataType.NUMERIC;
                break;
            case Types.BLOB:
                dataType = ORDataType.BLOB;
                break;
            case Types.CLOB:
                dataType = ORDataType.CLOB;
                break;
            default:
                dataType = ORDataType.VARCHAR;
        }
        preparedParameters.bindParam(connection.getORStream(), index, dataType, null);
    }

    @Override
    public void setBinaryStream(int index, InputStream x, int length) throws SQLException {
        verifyClosed();
        if (length < 0) {
            throw new SQLException("length " + length + " is Invalid.");
        }
        if (x == null) {
            setNull(index, Types.VARBINARY);
            return;
        }

        byte[] bs = new byte[length];
        try {
            int readLen = x.read(bs);
            if (readLen < length) {
                throw new SQLException("length " + length + " is Invalid.");
            }
            setBytes(index, bs);
        } catch (IOException e) {
            throw new SQLException("set binaryStream failed.");
        }
    }

    @Override
    public void clearParameters() {
        preparedParameters.clear();
    }

    @Override
    public void setDate(int index, Date x) throws SQLException {
        verifyClosed();
        setDate(index, x, null);
    }

    @Override
    public void setObject(int index, Object x, int targetSqlType) throws SQLException {
        setObject(index, x, targetSqlType, -1);
    }

    @Override
    public void setObject(int index, Object x) throws SQLException {
        verifyClosed();
        if (x == null) {
            setNull(index, 0);
        } else if (x instanceof String || x instanceof Character) {
            setObjectOfString(index, x);
        } else if (x instanceof BigDecimal || x instanceof Short || x instanceof Integer
                || x instanceof Long || x instanceof Float || x instanceof Double) {
            setObjectOfFigure(index, x);
        } else if (x instanceof byte[] || x instanceof Byte) {
            setObjectOfByte(index, x);
        } else if (x instanceof java.sql.Date || x instanceof Time || x instanceof Timestamp) {
            setObjectOfDate(index, x);
        } else if (x instanceof Boolean) {
            setBoolean(index, (Boolean) x);
        } else if (x instanceof Blob) {
            setBlob(index, (Blob) x);
        } else if (x instanceof Clob) {
            setClob(index, (Clob) x);
        } else if (x instanceof Array) {
            setArray(index, (Array) x);
        } else if (x instanceof Number) {
            setNumber(index, (Number) x);
        } else if (x instanceof InputStream) {
            setBinaryStream(index, (InputStream) x);
        } else {
            throw new PSQLException(GT.tr(
                    "Can''t infer the SQL type to use for an instance of {0}. Use "
                            + "setObject() with an explicit Types value to specify the type to use.",
                    x.getClass().getName()), PSQLState.INVALID_PARAMETER_TYPE);
        }
    }

    private void setObjectOfString(int index, Object x) throws SQLException {
        if (x instanceof Character) {
            setString(index, ((Character) x).toString());
        } else {
            setString(index, x.toString());
        }
    }

    private void setObjectOfFigure(int index, Object x) throws SQLException {
        if (x instanceof BigDecimal) {
            setBigDecimal(index, (BigDecimal) x);
        } else if (x instanceof Short) {
            setShort(index, (Short) x);
        } else if (x instanceof Integer) {
            setInt(index, (Integer) x);
        } else if (x instanceof Long) {
            setLong(index, (Long) x);
        } else if (x instanceof Float) {
            setFloat(index, (Float) x);
        } else if (x instanceof Double) {
            setDouble(index, (Double) x);
        } else {
            setObject(index, x);
        }
    }

    private void setNumber(int index, Number x) throws SQLException {
        verifyClosed();
        if (x == null) {
            setNull(index, Types.DECIMAL);
        } else {
            preparedParameters.bindParam(connection.getORStream(), index, ORDataType.NUMERIC, x);
        }
    }

    private void setObjectOfByte(int index, Object x) throws SQLException {
        if (x instanceof byte[]) {
            setBytes(index, (byte[]) x);
        } else if (x instanceof Byte) {
            setByte(index, (Byte) x);
        } else {
            setObject(index, x);
        }
    }

    private void setObjectOfDate(int index, Object x) throws SQLException {
        if (x instanceof java.sql.Date) {
            setDate(index, (java.sql.Date) x);
        } else if (x instanceof Time) {
            setTime(index, (Time) x);
        } else if (x instanceof Timestamp) {
            setTimestamp(index, (Timestamp) x);
        } else {
            setObject(index, x);
        }
    }

    @Override
    public void setNCharacterStream(int index, Reader value) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setNCharacterStream(int,Reader)");
    }

    @Override
    public void setCharacterStream(int index, Reader reader, int length) throws SQLException {
        verifyClosed();
        if (reader == null) {
            setNull(index, Types.VARCHAR);
            return;
        }
        if (length < 0) {
            throw new PSQLException(GT.tr("Invalid stream length {0}.", length),
                    PSQLState.INVALID_PARAMETER_VALUE);
        }
        setString(index, readerToString(reader, length));
    }

    @Override
    public void setCharacterStream(int index, Reader reader) throws SQLException {
        setCharacterStream(index, reader, Integer.MAX_VALUE);
    }

    @Override
    public void setCharacterStream(int index, Reader reader, long length) throws SQLException {
        setCharacterStream(index, reader, (int) length);
    }

    @Override
    public void setRowId(int index, RowId x) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setRowId(int,RowId)");
    }

    private String readerToString(Reader value, int maxLength) throws SQLException {
        try {
            int bufferSize = Math.min(maxLength, 1024);
            StringBuilder result = new StringBuilder(bufferSize);
            char[] buf = new char[bufferSize];
            int nRead = 0;
            while (nRead > -1 && result.length() < maxLength) {
                nRead = value.read(buf, 0, Math.min(bufferSize, maxLength - result.length()));
                if (nRead > 0) {
                    result.append(buf, 0, nRead);
                }
            }
            return result.toString();
        } catch (IOException ioe) {
            throw new PSQLException(GT.tr("Provided Reader failed."), PSQLState.UNEXPECTED_ERROR, ioe);
        }
    }

    @Override
    public void setRef(int index, Ref x) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setRef(int,Ref)");
    }

    @Override
    public void setBlob(int index, Blob x) throws SQLException {
        verifyClosed();
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.BLOB, x);
    }

    @Override
    public void setClob(int index, Reader reader) throws SQLException {
        setCharacterStream(index, reader, Integer.MAX_VALUE);
    }

    @Override
    public void setClob(int index, Reader reader, long length) throws SQLException {
        if (length < 0) {
            throw new SQLException("parameter length can not be less than 0");
        } else if (length > Integer.MAX_VALUE) {
            throw new SQLException("parameter length can not be more than " + Integer.MAX_VALUE);
        } else {
            setCharacterStream(index, reader, (int) length);
        }
    }

    @Override
    public void setClob(int index, Clob x) throws SQLException {
        verifyClosed();
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.CLOB, x);
    }

    @Override
    public void setNClob(int index, Reader reader, long length) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setNClob(int,Reader,long)");
    }

    @Override
    public void setNClob(int index, NClob value) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setNClob(int,NClob)");
    }

    @Override
    public void setTimestamp(int index, Timestamp x) throws SQLException {
        verifyClosed();
        setTimestamp(index, x, null);
    }

    @Override
    public void setArray(int index, Array x) throws SQLException {
        verifyClosed();
        if (x == null) {
            setNull(index, Types.ARRAY);
            return;
        }
        this.setString(index, x.toString());
    }

    @Override
    public ResultSetMetaData getMetaData() throws SQLException {
        verifyClosed();
        if (rs == null) {
            execute();
            if (rs == null) {
                return null;
            }
        }
        return rs.getMetaData();
    }

    @Override
    public void setURL(int index, URL x) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setURL(int,URL)");
    }

    @Override
    public void setDate(int index, Date x, Calendar cal) throws SQLException {
        verifyClosed();
        if (x == null) {
            setNull(index, Types.DATE);
            return;
        }
        Calendar c = cal == null ? Calendar.getInstance() : cal;
        long date = TimestampUtils.setDateToLong(x.getTime(), c);
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.DATE, date);
    }

    @Override
    public void setTime(int index, Time x, Calendar cal) throws SQLException {
        verifyClosed();
        if (x == null) {
            setNull(index, Types.TIME);
            return;
        }
        Calendar c = cal == null ? Calendar.getInstance() : cal;
        long time = TimestampUtils.setTimeToLong(x.getTime(), c);
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.TIME, time);
    }

    @Override
    public void setTimestamp(int index, Timestamp x, Calendar cal) throws SQLException {
        verifyClosed();
        if (x == null) {
            setNull(index, Types.TIMESTAMP);
            return;
        }
        Calendar c = cal == null ? Calendar.getInstance() : cal;
        long timestamp = TimestampUtils.setTimestampToLong(x.getTime(), c);
        long value = x.getNanos() / 1000 % 1000 + timestamp;
        preparedParameters.bindParam(connection.getORStream(), index, ORDataType.TIMESTAMP, value);
    }

    @Override
    public ParameterMetaData getParameterMetaData() throws SQLException {
        verifyClosed();
        return new ORParameterMetaData(preparedQuery);
    }

    @Override
    public void setNull(int index, int sqlType, String typeName) throws SQLException {
        if (typeName == null) {
            setNull(index, sqlType);
        }
        throw Driver.notImplemented(this.getClass(), "setNull(int,int,String)");
    }

    @Override
    public void setNCharacterStream(int index, Reader value, long length) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setNCharacterStream(int,Reader,long)");
    }

    @Override
    public void setBlob(int index, InputStream inputStream, long length) throws SQLException {
        if (length < 0) {
            throw new SQLException("parameter length can not be less than 0");
        } else if (length > Integer.MAX_VALUE) {
            throw new SQLException("parameter length can not be more than " + Integer.MAX_VALUE);
        } else {
            setBinaryStream(index, inputStream, (int) length);
        }
    }

    @Override
    public void setSQLXML(int index, SQLXML xmlObject) throws SQLException {
        throw Driver.notImplemented(this.getClass(), "setSQLXML(int,SQLXML)");
    }

    @Override
    public void setObject(int index, Object x, int sqlType, int scale) throws SQLException {
        verifyClosed();
        if (x == null) {
            setNull(index, sqlType);
        }
        switch (sqlType) {
            case Types.SQLXML:
                setXML(index, x);
                break;
            case Types.INTEGER:
            case Types.TINYINT:
            case Types.SMALLINT:
                setInt(index, toInt(x));
                break;
            case Types.CHAR:
            case Types.VARCHAR:
            case Types.LONGVARCHAR:
                setString(index, toString(x));
                break;
            case Types.DOUBLE:
            case Types.FLOAT:
                setDouble(index, toDouble(x));
                break;
            case Types.BIGINT:
                setLong(index, toLong(x));
                break;
            case Types.DATE:
                setDate(index, x);
                break;
            case Types.TIME:
                setTime(index, x);
                break;
            case Types.TIMESTAMP:
                setTimestamp(index, x);
                break;
            case Types.BOOLEAN:
            case Types.BIT:
                setBoolean(index, BooleanTypeUtil.castToBoolean(x));
                break;
            case Types.REAL:
                setFloat(index, toFloat(x));
                break;
            case Types.BINARY:
            case Types.VARBINARY:
            case Types.LONGVARBINARY:
                setBinary(index, x);
                break;
            case Types.DECIMAL:
            case Types.NUMERIC:
                setBigDecimal(index, toBigDecimal(x, scale));
                break;
            case Types.BLOB:
                setBlob(index, x);
                break;
            case Types.CLOB:
                setClob(index, x);
                break;
            case Types.ARRAY:
                setArray(index, x);
                break;
            case Types.OTHER:
                setString(index, x.toString());
                break;
            default:
                throw new PSQLException(GT.tr("Unsupported target sql type: {0}", sqlType),
                        PSQLState.INVALID_PARAMETER_TYPE);
        }
    }

    private void setXML(int index, Object x) throws SQLException {
        if (x instanceof SQLXML) {
            setSQLXML(index, (SQLXML) x);
        } else {
                throw new PSQLException(GT.tr("Unsupported date type value: {0}", x),
                        PSQLState.INVALID_PARAMETER_TYPE);
        }
    }

    private void setDate(int index, Object x) throws SQLException {
        if (x == null) {
            setNull(index, Types.DATE);
            return;
        }
        java.sql.Date value;
        if (x instanceof java.sql.Date) {
            value = (java.sql.Date) x;
        } else if (x instanceof java.util.Date) {
            value = new java.sql.Date(((java.util.Date) x).getTime());
        } else {
            value = connection.getTimestampUtils().toDate(null, x.toString());
        }
        setDate(index, value);
    }

    private void setTime(int index, Object x) throws SQLException {
        if (x == null) {
            setNull(index, Types.TIME);
            return;
        }
        Time value;
        if (x instanceof java.sql.Time) {
            value = (java.sql.Time) x;
        } else if (x instanceof java.util.Date) {
            value = new java.sql.Time(((java.util.Date) x).getTime());
        } else {
            value = connection.getTimestampUtils().toTime(null, x.toString());
        }
        setTime(index, value);
    }

    private void setTimestamp(int index, Object x) throws SQLException {
        if (x == null) {
            setNull(index, Types.TIMESTAMP);
            return;
        }
        java.sql.Timestamp value;
        if (x instanceof java.sql.Timestamp) {
            value = (java.sql.Timestamp) x;
        } else if (x instanceof java.util.Date) {
            value = new java.sql.Timestamp(((java.util.Date) x).getTime());
        } else {
            value = connection.getTimestampUtils().toTimestamp(null, x.toString());
        }
        setTimestamp(index, value);
    }

    private void setBlob(int index, Object x) throws SQLException {
        if (x instanceof Blob) {
            setBlob(index, (Blob) x);
        } else if (x instanceof String) {
            byte[] value = ((String) x).getBytes(connection.getORStream().getCharset());
            setBytes(index, value);
        } else {
            setBinary(index, x);
        }
    }

    private void setClob(int index, Object x) throws SQLException {
        if (x instanceof Clob) {
            setClob(index, (Clob) x);
        } else if (x instanceof String) {
            setString(index, (String) x);
        } else if (x instanceof byte[]) {
            setBytes(index, (byte[]) x);
        } else {
            throw new PSQLException(
                    GT.tr("Cannot cast an instance of {0} to type {1}",
                            x.getClass().getName(), "Types.CLOB"),
                    PSQLState.INVALID_PARAMETER_TYPE);
        }
    }

    private void setArray(int index, Object x) throws SQLException {
        if (x == null) {
            setNull(index, Types.VARCHAR);
            return;
        }
        if (x instanceof Array) {
            setArray(index, (Array) x);
        } else {
            throw new PSQLException(
                    GT.tr("Cannot cast an instance of {0} to type {1}",
                            x.getClass().getName(), "Types.ARRAY"),
                    PSQLState.INVALID_PARAMETER_TYPE);
        }
    }

    private int toInt(Object in) throws SQLException {
        try {
            if (in instanceof Number) {
                return ((Number) in).intValue();
            }
            if (in instanceof Boolean) {
                return (Boolean) in ? 1 : 0;
            }
            return Integer.parseInt(in.toString());
        } catch (IllegalArgumentException e) {
            throw new SQLException("data type convert failed.");
        }
    }

    private String toString(Object in) throws SQLException {
        try {
            if (in instanceof Clob) {
                Clob value = (Clob) in;
                return value.getSubString(1, (int) value.length());
            }
            return in.toString();
        } catch (IllegalArgumentException e) {
            throw new SQLException("data type convert failed.");
        }
    }

    private BigDecimal toBigDecimal(Object in, int scale) throws SQLException {
        try {
            BigDecimal bd;
            if (in instanceof BigDecimal) {
                bd = ((BigDecimal) in);
            } else if (in instanceof BigInteger) {
                bd = new BigDecimal((BigInteger) in);
            } else if (in instanceof Long || in instanceof Integer || in instanceof Short
                    || in instanceof Byte) {
                bd = BigDecimal.valueOf(((Number) in).longValue());
            } else if (in instanceof Double || in instanceof Float) {
                bd = BigDecimal.valueOf(((Number) in).doubleValue());
            } else {
                bd = new BigDecimal(in.toString());
            }
            if (scale >= 0) {
                bd = bd.setScale(scale, RoundingMode.HALF_UP);
            }
            return bd;
        } catch (IllegalArgumentException e) {
            throw new SQLException("data type convert failed.");
        }
    }

    private long toLong(final Object in) throws SQLException {
        if (in instanceof String || in instanceof Character) {
            return Long.parseLong(in.toString());
        }
        if (in instanceof Clob) {
            return Long.parseLong(toString(in.toString()));
        }
        if (in instanceof Number) {
            return ((Number) in).longValue();
        }
        throw new SQLException("data type convert failed.");
    }

    private float toFloat(Object in) throws SQLException {
        try {
            if (in instanceof Number) {
                return ((Number) in).floatValue();
            }
            if (in instanceof Boolean) {
                return (Boolean) in ? 1f : 0f;
            }
            if (in instanceof Clob) {
                return Float.parseFloat(in.toString());
            }
            return Float.parseFloat(in.toString());
        } catch (IllegalArgumentException e) {
            throw new SQLException("data type convert failed.");
        }
    }

    private double toDouble(Object in) throws SQLException {
        try {
            if (in instanceof Number) {
                return ((Number) in).doubleValue();
            }
            if (in instanceof Boolean) {
                return (Boolean) in ? 1d : 0d;
            }
            if (in instanceof Clob) {
                return Double.parseDouble(toString(in.toString()));
            }
            return Double.parseDouble(in.toString());
        } catch (IllegalArgumentException e) {
            throw new SQLException("data type convert failed.");
        }
    }

    @Override
    public void setAsciiStream(int index, InputStream x, long length) throws SQLException {
        throw new SQLException("setAsciiStream(int,InputStream,long) not implemented");
    }

    @Override
    public void setBinaryStream(int index, InputStream x, long length) throws SQLException {
        setBinaryStream(index, x, (int) length);
    }

    @Override
    public void setBinaryStream(int index, InputStream x) throws SQLException {
        verifyClosed();
        if (x == null) {
            setNull(index, Types.VARBINARY);
            return;
        }
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            byte[] bs = new byte[4096];
            int readLen = x.read(bs);
            while (readLen > 0) {
                os.write(bs, 0, readLen);
                readLen = x.read(bs);
            }
            setBytes(index, os.toByteArray());
        } catch (IOException e) {
            throw new SQLException("set binaryStream failed.");
        }
    }

    @Override
    public void setAsciiStream(int index, InputStream x) throws SQLException {
        throw new SQLException("setAsciiStream(int,InputStream) not implemented");
    }

    @Override
    public void clearBatch() {
        sqls.clear();
        parametersList.clear();
    }

    @Override
    public void setBlob(int index, InputStream inputStream) throws SQLException {
        setBinaryStream(index, inputStream);
    }

    @Override
    public void setNClob(int index, Reader reader) throws SQLException {
        throw new SQLException("setNClob(int,Reader) not implemented");
    }
}
